function update_text() {
  var text_div = document.getElementById("num_edges");
  text_div.innerHTML = "Number of edges: " + net.Edges.length;
}

function update_network_edges(node_edge_obj) {
  // grab the edges
  var edges = node_edge_obj.edges;

  // now update the network
  net.updateEdges(edges);
 
  // write the number of edges in the network 
  var text_div = document.getElementById("num_edges");

  text_div.innerHTML = "Number of edges: " + net.Edges.length;
}


function setShortestPathNode(idx) {
  // first, check to see if we're in "shortest path" mode
  if (!shortest_path_mode) { return false; }

  // keep track of whether we set an idx this time around
  var set_idx = false;

  // if the from node has been set and the index is the same as this one, then toggle that index off and return
  if (from_node == idx) {
    /*
    if (to_node != -1) {
      from_node = to_node;
      to_node = -1;
    }
    else {
      from_node = -1;
    }
    */
    //document.getElementById("img" + idx).src="images/author_node.png";
    resetSPNode(idx);
    //resetNodeImage(idx);
    
    if (to_node != -1) { resetSPNode(to_node); } //resetNodeImage(to_node); }

    // clear both nodes
    from_node = -1;
    to_node = -1;
    
    // might need to clear shortest path edges
    if (curShortestPathEdgeIdx.length > 0) {
      clearCurrentShortestPathEdges();
    }

    return false;
  }

  // if the to node is this node (updatd 7/14/10 -- do nothing)
  if (to_node == idx) {
    return false;
    to_node = -1;
    resetSPNode(idx);
    //resetNodeImage(idx);
    // might need to clear shortest path edges
    if (curShortestPathEdgeIdx.length > 0) {
      clearCurrentShortestPathEdges();
    }
    return false;
  }

  // in this case, the from node has not been set, so simply set it and return
  if (from_node == -1) {
    from_node = idx;
    setSPNodeToSource(idx);
    //setNodeImageToSource(idx);
    set_idx = true;
    // only return if to_node is unset
    if (to_node == -1) {
      return false;
    }
  }

  // handle the case that both are already set and we try to place a new idx (i.e., one that is
  // not equal to the current input idx) -- CHANGE to_node to match the input idx!
  if (from_node != -1 && to_node != -1 && !set_idx) {
    resetSPNode(to_node);
    //resetNodeImage(to_node);

    // might need to clear shortest path edges
    if (curShortestPathEdgeIdx.length > 0) {
      clearCurrentShortestPathEdges();
    }

    to_node = idx;
    setSPNodeToDest(idx);
    //setNodeImageToDest(idx);
    //return false;
  }

  // if we've gotten here, then from node is set and we aren't toggling the index, so set the to node and
  // find the shortest path
  if (to_node == -1) {
    to_node = idx;
    setSPNodeToDest(idx);
    //setNodeImageToDest(idx);
  }

  // find path
  drawShortestPath();

  return false;
}

// Resets a shortest path node
function resetSPNode(idx) {
  resetNodeImage(idx);
  
  if (from_node == idx) {
    document.getElementById("path-source").innerHTML = "";
  }
  else if (to_node == idx) {
    document.getElementById("path-dest").innerHTML = "";
  }

  document.getElementById("path-nodes").innerHTML = "";
}

// Sets a shortest path node to source
function setSPNodeToSource(idx) {
  setNodeImageToSource(idx);
  
  document.getElementById("path-source").appendChild(createSPNodeDiv(authorNames[idx], "source"));
  //innerHTML = "Source: " + authorNames[idx];
}

// Sets a shortest path node to destination
function setSPNodeToDest(idx) {
  setNodeImageToDest(idx);

  //document.getElementById("path-dest").appendChild(createSPNodeDiv(authorNames[idx], "dest"));
  //innerHTML = "Destination: " + authorNames[idx];
}

// Creates a div to display a shortest path node on the page
function createSPNodeDiv(name, type) {
  var div = document.createElement("DIV");

  // get this div's paragraph
  var p = document.createElement("P");
  // set the paragraph to the name of the author
  p.innerHTML = name;
  // style the anchor
  p.style.color = "#f3f2e8";
  p.style.font = "13pt Arial";
  p.style.fontWeight = "bold";
  p.style.width = "160px";

  // figure out how to vertically center this
  p.style.marginLeft = "7px";
  p.style.cssFloat = "left";

  // node image
  var img = document.createElement("IMG");

  // arrow image
  var arrow = document.createElement("IMG");
  var arrow_sz = 35;
  
  if (type == "source") {
    div.style.borderTop = "1px solid #f3f2e8";
    img.src = "images/author_node_source.png";
    img.style.marginLeft = "5px";

    arrow.src = "images/sp_arrow_source.png"; 
    arrow.style.width = arrow_sz + "px";
  }
  else if (type == "dest") {
    img.src = "images/author_node_dest.png";
    img.style.marginLeft = "5px";

    arrow.src = "images/sp_arrow_dest.png";
    arrow.style.width = arrow_sz + "px";
  }
  else if (type == "central") {
    img.src = "images/author_node.png";
    img.style.marginLeft = "35px";

    arrow.src = "images/sp_arrow_down.png";
    arrow.style.height = arrow_sz + "px";
  }

  // style nodes
  img.style.cssFloat = "left";
  img.style.width = "32px";
  img.style.height = "32px";
  img.style.marginTop = "15px";

  // style arrows
  arrow.style.cssFloat = "left";
  arrow.style.marginTop = "20px";
  arrow.style.marginRight = "5px";
 
  // style paragraph 
  p.style.cssFloat = "left";

  // style div
  div.style.height = "75px";
  div.style.width = "278px";
  div.style.paddingTop = "0px";
  div.style.paddingLeft = "0px";
  div.style.borderBottom = "1px solid #f3f2e8";
  div.style.marginLeft = "0px";
  div.style.marginTop = "auto";
  div.style.backgroundImage = "url('images/author_node_info_bg_tall.jpg')";
  div.style.backgroundRepeat = "repeat-x";

  div.appendChild(img);
  div.appendChild(p);
  div.appendChild(arrow);

  return div;
}

// Draws shortest path between nodes based on nodes set
function drawShortestPath() {
  // check the inputs
  if (from_node == -1 || to_node == -1) {
    return false;
  }
 
  // send an ajax query to get the edge list
  var opts = {
    type: "GET",
    url: "http://austin.cs.dartmouth.edu:8087/shortestPath",
    data: "from_node=" + authorNames[from_node] + "&to_node=" + authorNames[to_node],
    success: drawShortestPathAjax
  }

  $.ajax(opts);

  /*
  document.getElementById("img" + from_node).src = "images/author_node.png";
  document.getElementById("img" + to_node).src = "images/author_node.png";

  from_node = -1;
  to_node = -1;
  */
} 

// Draws edges returned based on shortest path calculation
function drawShortestPathEdges(edgeArray) {
  // clear shortest path edge idx array
  curShortestPathEdgeIdx = new Array();

  // options for how to draw edges
  var edgeOpts = {
    strokeColor: "#0000FF",
    strokeOpacity: 0.75,
    strokeWeight: 5,
    clickable: false,
    zIndex: 0 
  };

  // keep track of which nodes are in this path
  curShortestPathNodeIdx = new Array();
  curShortestPathNodeIdx.push(from_node);

  // loop through all edges, compare to those in edgeArray
  for (j = 0; j < edgeArray.length; j++) {
    for (i = 0; i < origEdges.length; i++) {
      // if we found a matching edge
      // N.B. when we're checking here, need to account for the fact that from/to order might have changed
      if (edgeArray[j].from == origEdges[i].from && edgeArray[j].to == origEdges[i].to) {
        // change its appearance
        authorEdges[i].setOptions(edgeOpts);
        // save this idx
        curShortestPathEdgeIdx.push(i);
        // save the node idx
        if (edgeArray[j].from == curShortestPathNodeIdx[curShortestPathNodeIdx.length-1]) {
          //curShortestPathNodeIdx[curShortestPathNodeIdx.length] = edgeArray[j].to;
          curShortestPathNodeIdx.push(edgeArray[j].to);
        }
        else {
          //curShortestPathNodeIdx[curShortestPathNodeIdx.length] = edgeArray[j].from;
          curShortestPathNodeIdx.push(edgeArray[j].from);
          //curShortestPathNodeIdx.concat(edgeArray[j].from);
        }
      }
      else if (edgeArray[j].from == origEdges[i].to && edgeArray[j].to == origEdges[i].from) {
        // change its appearance
        authorEdges[i].setOptions(edgeOpts);
        // save this idx
        curShortestPathEdgeIdx.push(i);
        // save the node idx
        if (edgeArray[j].from == curShortestPathNodeIdx[curShortestPathNodeIdx.length-1]) {
          //curShortestPathNodeIdx.concat(edgeArray[j].to);
          curShortestPathNodeIdx.push(edgeArray[j].to);
          //curShortestPathNodeIdx[curShortestPathNodeIdx.length] = edgeArray[j].to;
        }
        else {
          //curShortestPathNodeIdx.concat(edgeArray[j].from);
          curShortestPathNodeIdx.push(edgeArray[j].from);
          //curShortestPathNodeIdx[curShortestPathNodeIdx.length] = edgeArray[j].from;
        }
      }
    }
  }

  writeShortestPathInfo(curShortestPathNodeIdx);
}


// Closes any and all open info boxes
function closeInfoBoxes() {
  if (open_info_box != -1) {
    // might need to make sure that if a node's info box was open, it is def. closed
    if (was_map_click) {
      authorNodes[open_info_box].toggleInfoBox();
    }
    else {
      toggleInfoBox(open_info_box);
    }
  }

  open_info_box = -1;

  /*
  if (open_info_box_div != null) {
    closeInfoBox
  }
  */
}

// Generic click handler for node click events
// Switches functionality based on current mode
function registerNodeClick(idx) {
  registerClick(idx, true);

  return false;
}

// Generic click handler for info bar click events
function registerInfoClick(idx) {
  registerClick(idx, false);

  return false;
}


// Shows an info box for this node's author info
function toggleInfoBox(idx, rank_idx) {
  // in this case, we are closing the box that's open deliberately
  if (open_info_box == idx) {
    closeInfoBox(idx);
    resetNodeImage(idx);
    open_info_box = -1;
  }
  // in this case, there is an open (or closed) info box, and it isn't this one
  else if (open_info_box != idx) {
    if (open_info_box != -1) {
      closeInfoBox(open_info_box);
      resetNodeImage(open_info_box);
      open_info_box = -1;
    }

    open_info_box = idx;
    openInfoBox(idx, rank_idx);
    centerMap(imgmap, authorNodes[idx].getCenter());
    setNodeImageToSelected(idx);
  }
}


// Opens an info box and places requisite info inside
function openInfoBox(idx, rank_idx) {
  // quick check to make sure that if we're going to open this info box, then
  // its corresponding anchor should be showing
  document.getElementById("name_field").childNodes[rank_idx].style.display = "";

  var cur_div = document.getElementById("info"+idx);

  if (cur_div.style.visibility == "hidden") {
    var height = 135;
    // get neighbors
    var nbrs = getNodeNeighbors(idx);
    // clear the current div 
    cur_div.innerHTML = "";
    // set anchor click fn
    var onclick_fn = "centerMap(imgmap, authorNodes[" + idx  + "].getCenter()); return false;";
    //var new_div = createAnchorControlDiv("", "center map", "#", onclick_fn);
    //new_div.style.paddingBottom = "5px";
    //cur_div.appendChild(new_div);

    // padding space + text
    var p = document.createElement("P");
    //p.style.font = "16pt Helvetica #3d3d3d";
    p.style.fontSize = "12pt";
    p.style.fontWeight = "bold";
    p.style.paddingBottom = "0px";
    p.innerHTML = authorNames[idx];
    cur_div.appendChild(p);

    var p2 = document.createElement("P");
    p2.style.paddingBottom = "0px";
    p2.innerHTML = "Neighbors:";
    cur_div.appendChild(p2);

    // now write in neighbors
    for (i=0; i < nbrs.length; i++) {
      //var a = createAnchorControlDiv("", authorNames[nbrs[i]], "#", "toggleInfoBox(" + nbrs[i] + ", " + authorIdxToRank[nbrs[i]]+ ");");
      var a = createAnchorControlDiv("", authorNames[nbrs[i]], "#", "registerInfoClick(" + nbrs[i] + ");");
      //cur_div.innerHTML += + "<br>";
      cur_div.appendChild(a);
      height += 15;
    }

    cur_div.innerHTML += "<br>";

    // add buttons
    // center map
    /*
    cur_div.innerHTML += "<a href='#' id='info_sub_link' onclick='" + onclick_fn + "'><img src='images/center_button.png' border='0'></a>";
    // more info
    cur_div.innerHTML += "<a href='http://en.wikipedia.org/w/index.php?search=" + authorNames[idx] + "' target='_blank'><img src='images/more_info_button.png' border='0'></a>";
    */

    // a little 'close' anchor:
    cur_div.innerHTML += "<br>";
    cur_div.innerHTML += "<br>";
    //cur_div.innerHTML += "<a id='info_sub_link_sub' href='#' onclick='toggleInfoBox(" + idx + "); return false;'>[x] close</a>";
    cur_div.innerHTML += "<a id='info_sub_link_sub' href='#' onclick='return registerInfoClick(" + idx + ");'>[x] close</a>";
    // set div style params
    cur_div.style.visibility = "visible";
    cur_div.style.display = "";
    cur_div.style.height = height+"px";
    // make variable width
    var width = p.innerHTML.length * 10;
    if (width < 235) {
      width = 235;
    }
    //set width
    cur_div.style.width = width + "px";
    //cur_div.style.width = "240px";
    cur_div.style.paddingLeft = "7px";
    //cur_div.style.paddingTop = "5px";
    cur_div.style.marginLeft = -(width+15) + "px";
    cur_div.style.marginTop = "-10px";
    cur_div.style.border = "1px solid black";
    cur_div.style.backgroundColor = "#ffffff";
  }
}


// Creates an info box div elt w/ specified options
function createInfoBox(idx) {
  var div = document.createElement("DIV");
  div.id = "info" + idx;
  div.style.visibility = "hidden";
  div.style.width = "100%";
  div.style.height = "0px";
  div.style.overflow = "auto";
  div.style.position = "absolute";
  div.style.zIndex = "2";
  
  return div;
}

// Closes an open info box
function closeInfoBox(idx, cur_div) {
  var cur_div = document.getElementById("info"+idx);

  cur_div.style.visibility = "hidden";
  cur_div.style.marginTop = "0px";
  cur_div.style.marginBottom = "0px";
  cur_div.style.height = "0px";
}

// Clears current shortest path edges
function clearCurrentShortestPathEdges() {
  // options for how to draw edges
  var edgeOpts = {
    strokeColor: "#FF0000",
    strokeOpacity: 0.5,
    strokeWeight: 2,
    clickable: false,
    zIndex: 0 
  };

  for (j = 0; j < curShortestPathEdgeIdx.length; j++) {
    i = curShortestPathEdgeIdx[j];
    authorEdges[i].setOptions(edgeOpts);
  }
}

function writeShortestPathInfo(curPathNodeIdx) {
  cur_div = document.getElementById("path-nodes");

  cur_div.innerHTML = "";

  // only write "central" nodes -- i.e., don't rewrite source/dest
  for (i=1; i < curPathNodeIdx.length-1; i++) {
    //cur_div.innerHTML += (i+1) + ") " + authorNames[curPathNodeIdx[i]] + "<br>";
    cur_div.appendChild(createSPNodeDiv(authorNames[curPathNodeIdx[i]], "central"));
  }

  document.getElementById("path-dest").appendChild(createSPNodeDiv(authorNames[curPathNodeIdx[curPathNodeIdx.length-1]], "dest"));
}

// Clears shortest path information
function clearShortestPathInfo() {
  // kludge 7/14/10
  path_nodes = [from_node, to_node];

  for (i = 0; i < path_nodes.length; i++) {
    if (path_nodes[i] != -1) {
      resetNodeImage(path_nodes[i]);
    }
  }

  from_node = -1;
  to_node = -1;
  path_nodes = new Array();

  // just in case, we should clear the current edge idx
  curShortestPathEdgeIdx = new Array();

  // clear out the div that contains info about the nodes in the path
  document.getElementById("path-nodes").innerHTML = "";
  document.getElementById("path-source").innerHTML = "";
  document.getElementById("path-dest").innerHTML = "";
}

// Sets an author node image to the original node image
function resetNodeImage(idx) {
  //document.getElementById("img" + idx).src="images/author_node.png";
  authorNodes[idx].resetImage();
}

// Sets an author node image to selected
function setNodeImageToSelected(idx) {
  //document.getElementById("img" + idx).src="images/author_node_select.png";
  authorNodes[idx].setSelectImage();
}

function setNodeImageToDest(idx) {
  authorNodes[idx].setDestImage();
}

// Sets an author node image to source
function setNodeImageToSource(idx) {
  authorNodes[idx].setSourceImage();
}
